//
//  sstUdp.m
//  OpensslTest
//
//  Created by jiahua on 16/3/30.
//  Copyright © 2016年 secsmarts. All rights reserved.
//

#import "sstUdp.h"
#import "sstComstruct.h"
#import "sstNSDataFormat.h"
#import "sstNSStringFormat.h"
#import "HttpClient.h"
#import "KeyManage.h"
#import "SSTInitFactory.h"
#import <arpa/inet.h>
#include "Define.h"
#include "DeviceManager.h"
#import "GCDUtil.h"

@interface sstUdp()
//
// UDP协议发包过程中包的编号
//
@property(nonatomic)NSInteger num;

@end
@implementation sstUdp

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark ReceiveMessage
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
 *  操作接收到的 NSData 格式数据，进行具体的拆包解密工作。假设秘钥协商已经完成
 *
 *  @param data    <#data description#>
 *  @param address <#address description#>
 *
 *  @return 返回拆包解密后的明文数据
 */
-(NSData*)handReceiveData:(NSData*)data withAddress:(NSData *)address{
    
    u8 outData[SST_SEC_SIZE] = {0};
    int outDataLen = 0;
    int bufferDataLen = (int)[data length];
    
    NSString * ip = [GCDUtil getIPAddressFromAddress: address];
    NSLog(@"UDP数据【来源】IP --> %@", ip);
    u8* bufferData = (u8*)[data bytes];//收到的即将用来操作的数据
    int isEncrypt = isEncryptPackage(bufferData);//用来判断新旧家电
    
    //1、首先判断是新家电还是旧家电
    if (isEncrypt == 0){
        //如果是旧家电，那么标记下来直接返回明文
        [[DeviceManager Instance] setIsLegacyDeviceByIp: ip];
        return data;
    }
    //Boolean isBroadCase = [self isBroadCase: ip];
    //对于新家电，把数据后后面16字节截取下来
    NSString *ipData = [NSString stringWithFormat:@"收到消息: %@", ip];
    LogHex((const char *)[sstNSStringFormat NSString2Char: ipData], bufferData, bufferDataLen);
    u8 *hexTailStr = [KeyManage getTail16String: bufferData dataLen: bufferDataLen];
    
    SstDevice *device = [[DeviceManager Instance] getSstDeviceByIp: ip];
    //本来该先通过Cmd判断是广播还是单播
    //然后通过判断尾部16字节是否为全0，来分辨家电状态；或者看单播是否加密
    //由于解密函数中已经有区分Cmd的代码部分，这里只先通过尾部来标记一下家电状态。
    //判断尾部16字节是否全0
    //这里只是做一个标记，方便发送的时候进行判断
    if (isBroadCast(bufferData))
    {
        [[DeviceManager Instance] setDeviceUdpKeyIdByIP: ip newUdpKeyId: hexTailStr];
    }
    
    //udp接收消息的三种情况：
    //1）接收消息要么是广播，有没有拿到key都会明文解析。广播会一直开着，忽略。
    //2）接收正常消息，使用协商完的秘钥进行解密
    SstSecHead* Sp2 = (SstSecHead*)bufferData;
    //去掉尾部16个字节的UdpId
    bufferDataLen -= UDP_KEY_ID_LEN;
    Sp2->Len = htons(ntohs(Sp2->Len) - UDP_KEY_ID_LEN);
    
    //正常业务数据包：communicateTagCmd==6或者广播
    int num = (int)self.num;
    int resultTag = myDestructSecPacketRaw(bufferData, bufferDataLen, device.udpKey, HASH_DIGEST_LEN, &num, outData, &outDataLen);
    NSString *recvFromUDP = [sstNSStringFormat Char2NSString: outData];
    NSLog(@"recvFromUDP = %@", recvFromUDP);
    
    //非广播的情况下num才有变化
    if(![GCDUtil isBroadCaseByIp: ip])
        self.num = num;
    
    if(resultTag < 0){
        NSLog(@"UDP拆包解密失败 -->【退出】");
        //用来判断用户是否实现了此代理方法
        SEL selector = @selector(sstUdpError:withTag:);
        __strong id theDelegate = self.udpErrorDelegate;
        if ([theDelegate respondsToSelector:selector]){
            [self.udpErrorDelegate sstUdpError:self withTag:(NSInteger)(-1*resultTag)];
        }
        return nil;
    }
    NSLog(@"UDP包解密成功");
    
    //如果是正常通信，那么数据正常返回
    return [sstNSDataFormat Char2NSData:outData withLen:outDataLen];
}

/**
 *  接收数据
 *
 *  @param data    <#data description#>
 *  @param address <#address description#>
 *  @param context <#context description#>
 */
- (void)notifyDidReceiveData:(NSData *)data fromAddress:(NSData *)address withFilterContext:(id)context{
    [super notifyDidReceiveData:[self handReceiveData:data withAddress:address] fromAddress:address withFilterContext:context];
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark SendMessage
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 *  对数据进行加密装包
 *
 *  @param data data description
 *
 *  @return 返回加密装包后的数据
 */
-(NSData*)handSendData:(NSData*)data{
    int iRtn = SST_OK;
    u8 *outU8 = NULL;
    int outDataLen = 0;
    u8 SecTrans = 1;//用来判断发包时是否进行加密。1是加密，0是不加密
    u8 cmdTag;//用来表示发送数据时，包头cmd的值。0协商秘钥发送，6发送业务数据
    NSString *ip = [self host];
    int resultTag = -1;
    u8* bufferData = [sstNSDataFormat NSData2Byte: data];//转换成即将进行操作加密的数据类型
    LogHex("bufferData---->", bufferData, 32);
    //1、首先看是否已经协商完秘钥能够通信，每次只在发送数据前进行秘钥协商
    //还有一种可能是tag==NOENCRYPT_TAG的情况，也就是下面给排除的情况
    SstDevice *device = [[DeviceManager Instance] getSstDeviceByIp: ip];
        //协商秘钥的条件
    //3、协商并在KeyManage中保存秘钥，然后进行通信
    //已完成秘钥协商，可以开始发送UDP请求了
    if([ip isEqualToString: @"255.255.255.255"])
        return data;
    
    if (device.isLegacy)// 旧家电，直接返回
        return  data;
        
    cmdTag = 0;
    
    if(!device.isUdpHandShaked)
    {
        device.port = [self port];
        // 1. send r1 encode by hash(uuid + password + mac)
        iRtn = [self doHandShakeWithDevice: device];
        sleep(1);
    }
    if(!device.isUdpHandShaked){
        //用来判断用户是否实现了此代理方法
        [self callErrorDelegageWithErrorTag: iRtn];
        return nil;
    }
    else
    {  //用来判断用户是否实现了此代理方法
        SEL selector = @selector(sstInfoNotify:withInfo:);
        __strong id theDelegate = self.udpInfoNotifyDelegate;
        if ([theDelegate respondsToSelector:selector]){
            [self.udpInfoNotifyDelegate sstInfoNotify:self withInfo: [sstNSStringFormat Byte2HexNSString: device.k1 withLength:LAN_KEY_LEN]];
        }

    }
    cmdTag = CMD_DATA;
    int num = (int)self.num;
    outU8 = malloc(data.length + HASH_DIGEST_LEN * 2);
    resultTag = myConstructSecPacketRaw([sstNSDataFormat NSData2Byte: data], (int)data.length, device.udpKey, HASH_DIGEST_LEN, &num, SecTrans, cmdTag, outU8, &outDataLen);
    self.num = num;
    
    if(resultTag < 0){
        //如果加密失败那么就发送空数据
        NSLog(@"UDP加密装包失败 -->【退出】");
        [self callErrorDelegageWithErrorTag:iRtn];
        free(outU8);
        return nil;
    }
    NSData *outData = [sstNSDataFormat Char2NSData:outU8 withLen:outDataLen];
    free(outU8);
    
    return outData;
}

/**
 *  发送数据1
 *
 *  @param data       <#data description#>
 *  @param remoteAddr <#remoteAddr description#>
 *  @param timeout    <#timeout description#>
 *  @param tag        <#tag description#>
 */
-(void)sendData:(NSData *)data toAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout tag:(long)tag{
    NSString * host = [GCDUtil getIPAddressFromAddress: remoteAddr];
    [self setHost: host];
    SstDevice *device = [[DeviceManager Instance] getSstDeviceByIp: host];
    //首先看是不是老机器
    if(device.isLegacy){
        //如果是老机器，那么不加密，不打包成sst
        [super sendData:data toAddress: remoteAddr withTimeout: timeout tag: tag];
    }else{
        [super sendData:[self handSendData:data] toAddress:remoteAddr withTimeout:timeout tag:tag];
    }
}

/**
 *  发送数据2
 *
 *  @param data    <#data description#>
 *  @param host    <#host description#>
 *  @param port    <#port description#>
 *  @param timeout <#timeout description#>
 *  @param tag     <#tag description#>
 */
-(void)sendData:(NSData *)data toHost:(NSString *)host port:(uint16_t)port withTimeout:(NSTimeInterval)timeout tag:(long)tag{
    //
    //先写死配置数据，用来测试完成初步的通信验证
    //
    [self setHost: host];
    [self setPort: port];
    //首先看是不是老机器
    
    Boolean isLegacyDevice = [[DeviceManager Instance] getIsLegacyDevice: host];
   
    if(isLegacyDevice){
        //如果是老机器，那么不加密，不打包成sst
        [super sendData: data toHost:host port:port withTimeout:timeout tag:tag];
    }else{
        
        [super sendData: [self handSendData:data] toHost:host port:port withTimeout:timeout tag:tag];
        NSLog(@"UDP数据[目的地]%@:%d--->%@", host, port, [sstNSDataFormat NSData2NSString: data]);
    }
}

/**
 *  发送数据
 *
 *  @param data    data description
 *  @param timeout timeout description
 *  @param tag     tag description
 */
-(void)sendData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag{
    [super sendData:[self handSendData:data] withTimeout:timeout tag:tag];
}

//
/**
 *  同家电多秘钥协商，并且接收数据
 *
 *  @param device    家电信息
 *  @param timeout   家电IP
 *  @param tag     tag description
 */
-(int) doHandShakeWithDevice: (SstDevice *)device
{
    int iRtn = SST_OK;
    u8 * r3 = nil;
    int r3len = 0;
    u8 * inData = nil;
    int inDataLen = 0;
    int k1len = 0;
    int outDataLen = 0;
    NSString *ip = device.ip;
    int port = device.port;
    
    if (device == nil)
    {
        iRtn = SST_NO_DEVICE_INFO_ERROR;
        return iRtn;
    }
    
    k1len = HASH_DIGEST_LEN;
    
    if(device.hasGetR3)
    {//家电已经获取R3，直接发送token.
        inData = device.hexToken;
        inDataLen = TOKEN_LENGTH;
    }
    else
    {//家电未获取R3，根据SSID协商秘钥。
        inData = device.k1;
        r3 = device.ssidKey;
        r3len = HASH_DIGEST_LEN;
        inDataLen = LICENSE_INIT_KEY_LEN;
    }
    u8 *outU8 = (u8*)malloc(SST_HEAD_SIZE + LICENSE_INIT_KEY_LEN + SST_HASH_SIZE);
    LogHex("K1", (const u8 *)device.k1, LICENSE_INIT_KEY_LEN);
    getHelloToDevicePackage(inData, inDataLen, r3, r3len, outU8, &outDataLen);
    u8 outBuf[256] = {0};
    iRtn = [self sendHelloAndGetR2FromDevice: outU8 buflen: outDataLen toHost:ip port:port outbuf:outBuf outBufLen: &outDataLen];
    if (iRtn != SST_OK)
    {
        free(outU8);
        NSLog(@"doHandShakeWithDevice error, sendDataToDevice return %d", iRtn);
        return iRtn;
    }
    u8 *r2buf = (u8*)malloc(SST_HEAD_SIZE + LICENSE_INIT_KEY_LEN + SST_HASH_SIZE + UDP_KEY_ID_LEN);
    iRtn = getDeviceAnswer(outBuf, outDataLen, device.k1, k1len, r2buf, &outDataLen, SST_CMD_UDP_DATA);
    if (iRtn == SST_OK)
    {
        device.udpK2 = r2buf;
    }
    else {
        NSString *outData = [sstNSStringFormat Char2NSString: r2buf];
        NSLog(@"getDeviceAnswer 失败, return =%@", outData);
        [device clearUdpKey];
    }
    free(r2buf);
    return iRtn;
}

-(int) sendHelloAndGetR2FromDevice: (u8 *)buffer buflen: (int) buflen toHost:(NSString *)host port:(uint16_t)port outbuf: (u8*)outBuf outBufLen:(int*)outBufLen
{
    int iRtn = SST_OK;
    ssize_t len;
    int clientSocketId;
    socklen_t addrlen;
    struct sockaddr_in client_sockaddr;
      // 第一步：创建Socket
    clientSocketId = socket(AF_INET, SOCK_DGRAM, 0);
    if(clientSocketId < 0) {
        NSLog(@"creat client socket fail\n");
        return SST_HANDSHAKE_ERROR;
    }
    
    addrlen = sizeof(struct sockaddr_in);
    bzero(&client_sockaddr, addrlen);
    client_sockaddr.sin_family = AF_INET;
    client_sockaddr.sin_addr.s_addr = inet_addr([host UTF8String]);
    client_sockaddr.sin_port = htons(port);
   
    // 第二步：发送消息到服务端
    // 将字符串传送给device端
    LogHex("sendto buffer", buffer, buflen);
    len = sendto(clientSocketId, buffer, buflen, 0, (struct sockaddr *)&client_sockaddr, addrlen);
    struct sockaddr_in sin;
    addrlen = sizeof(client_sockaddr);
    if(getsockname(clientSocketId, (struct sockaddr *)&sin, &addrlen) == 0) {
        char   buf[256] = {0};
        inet_ntop(AF_INET, &sin.sin_addr, buf, 256);
     }
    
    close(clientSocketId);
    if (len > 0)
    {
         NSLog(@"发送秘钥协商到--->%@:%d", host, port);
    }
    else
    {
        NSLog(@"发送秘钥协商到--->%@:%d失败", host, port);
        iRtn = SST_HANDSHAKE_ERROR;
        return iRtn;
    }
    
    ///////////////////--------
    struct sockaddr_in ser_sockaddr;
    int sersockfd = socket(AF_INET, SOCK_DGRAM,0);
    if(sersockfd < 0)
    {
        printf("I cannot socket success\n");
        return  SST_SOCKET_INIT_ERROR;
    }
    /*填写sockaddr_in 结构*/
    bzero(&ser_sockaddr, sizeof(ser_sockaddr));
    ser_sockaddr.sin_family = AF_INET;
    ser_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    ser_sockaddr.sin_port = sin.sin_port;
    /*绑定客户端*/
    iRtn = bind(sersockfd,(struct sockaddr *)&ser_sockaddr, sizeof(struct sockaddr_in));
    if(iRtn < 0)
    {
        iRtn = SST_SOCKET_BIND_ERROR;
        return iRtn;
    }
    
    struct timeval timeout={TIME_OUT,   0};//3s
    setsockopt(sersockfd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout));
    UInt64 msecond1 = [[NSDate date] timeIntervalSince1970];
    
    char buf[256] = {0};
    len = recvfrom(sersockfd, buf, 256,0,(struct sockaddr*)&ser_sockaddr, &addrlen);
    if(len < 0)
    {
        iRtn = SST_HANDSHAKE_ERROR;
        return iRtn;
    }
    *outBufLen = (int)len;
    memcpy(outBuf, buf, *outBufLen);
    UInt64 msecond2 = [[NSDate date] timeIntervalSince1970];
    NSLog(@"耗时：%llu", (msecond2 - msecond1));
    
    return iRtn;
}

-(void)callErrorDelegageWithErrorTag: (NSInteger) iRtn
{
    SEL selector = @selector(sstUdpError:withTag:);
    __strong id theDelegate = self.udpErrorDelegate;
    if ([theDelegate respondsToSelector:selector]){
        [self.udpErrorDelegate sstUdpError:self withTag: iRtn];
    }
}
@end
